////////////////////////////////////////////////////////////////////////////////
//
// (C) Andy Thomason 2011
//
// Example of drawing a triangle using OpenGL ES 2

#ifdef WIN32
  // This is the "GL extension wrangler"
  // Note: you will need to use the right version of glew32s.lib (32 bit vs 64 bit)
  #define GLEW_STATIC
  #include "GL/glew.h"

  // This is the "GL utilities" framework for drawing with OpenGL
  #define FREEGLUT_STATIC
  #define FREEGLUT_LIB_PRAGMAS 0
  #include "GL/glut.h"
#else
  #include "GLUT/glut.h"
  #include "GL/glew.h"
#endif

#include <stdio.h>
#include <math.h>
#include <assert.h>
#include <vector>

#include <vector.h>
#include <matrix.h>
#include <file_manager.h>
#include <texture_manager.h>

#include <shader.h>
#include <texShader.h>

class box 
{
protected:
	vec4 center_;
	vec4 half_extents_;
	vec4 color_;
public:
  //box();
  box()
  {
	center_= vec4(0, 0, 0, 0);
	half_extents_ = vec4(0, 0, 0, 0);
	color_ = vec4(0, 0, 0, 0);
  }

  box(float centerX, float centerY, float halfX, float halfY, vec4 color)
  {
	init(centerX,centerY,halfX,halfY,color);
  }

  void init(float cx, float cy, float hx, float hy, vec4 color) 
  {
		// note that it is a convention for positions and colors
		// to have "1" in the w, distances to have "0" in the w.
		center_ = vec4(cx, cy, 0, 1);
		half_extents_ = vec4(hx, hy, 0, 0);
		color_ = color;//vec4(1, 1, 1, 1);
  }

  void draw(shader &shader) 
  {
		// set the uniforms
		shader.render(color_);

		// set the attributes    
		float vertices[4*2] = {
			center_[0] - half_extents_[0], center_[1] - half_extents_[1],
			center_[0] + half_extents_[0], center_[1] - half_extents_[1],
			center_[0] + half_extents_[0], center_[1] + half_extents_[1],
			center_[0] - half_extents_[0], center_[1] + half_extents_[1],};

		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2*sizeof(float), (void*)vertices );
		glEnableVertexAttribArray(0);

		// kick the draw
		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
  }
  
  void move(const vec4 &dir) 
  {
    center_ += dir;
  }
  
  vec4 pos() const 
  { 
	  return center_;
  }
  
  void set_pos(vec4 v) 
  { 
	  center_ = v; 
  }
  
  virtual bool intersects(const box &rhs) 
  {
    vec4 diff = (rhs.pos() - pos()).abs();
    vec4 min_distance = rhs.half_extents_ + half_extents_;
    return diff[0] < min_distance[0] && diff[1] < min_distance[1];
  }
};

class paddle : public box
{
private:
	int numOfLives;
	//vec4 paddleColor;

public:
	paddle()
		:box()
	{
		numOfLives = 0;
	}

	paddle(float centerX, float centerY, float halfX, float halfY, vec4 color)
	//call the box constructor to initialize th paddle
	:box(centerX, centerY, halfX, halfY,color)
	{	
		numOfLives = 5;
	}

	void reset()
	{
		
	}

	int getLives() const
	{
		return numOfLives;
	}

	void setLives(int lives)
	{
		numOfLives = lives;
	}
};

class ball : public box
{
private:
	vec4 ballVelocity;
public:

	ball()
		:box()
	{}

	ball(float centerX, float centerY, float halfX, float halfY, vec4 color)
		//call the box constructor to initialize th paddle
		:box(centerX, centerY, halfX, halfY,color)
	{
	}

	vec4 getVelocity() const 
	{ 
		return ballVelocity; 
	}

	void setVelocity(vec4 vel)
	{
		ballVelocity = vel;
	}
};

class tile:public box
{
private:

public:
	tile()
		:box()
	{}

	tile(float centerX,float centerY,float halfX,float halfY,vec4 color)
		:box(centerX,centerY,halfX,halfY,color)
	{
		
	}
};

class backGround
{
	vec4 center_;
	vec4 half_extents_;
	vec4 color_;
	GLuint texture_;

public:
	backGround()
	{
		center_= vec4(0, 0, 0, 0);
		half_extents_ = vec4(0, 0, 0, 0);
		color_ = vec4(0, 0, 0, 0);
	}

	backGround(float centerX,float centerY,float halfX,float halfY,GLuint tex)
	{
		center_ = vec4(centerX, centerY, 0, 1);
		half_extents_ = vec4(halfX, halfY, 0, 0);
		texture_=tex;
	}

	void draw(texShader &shader,GLuint texture_) 
	{
		// set the uniforms
		shader.render();
		
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, texture_);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		// set the attributes    
		float vertices[4*2] = {
			center_[0] - half_extents_[0], center_[1] - half_extents_[1],
			center_[0] + half_extents_[0], center_[1] - half_extents_[1],
			center_[0] + half_extents_[0], center_[1] + half_extents_[1],
			center_[0] - half_extents_[0], center_[1] + half_extents_[1],};

		glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), (void*)vertices );
		glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 4*sizeof(float), (void*)(vertices + 2) );
		glEnableVertexAttribArray(0);
		glEnableVertexAttribArray(2);

		// kick the draw
		glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
		//glDisable(GL_BLEND);
	}
};

class levels
{
public:
	static const int firstLevel[81];
};

const int levels::firstLevel[] = {1,0,2,0,3,0,4,0,5,
								  0,5,0,4,0,3,0,2,0,
								  1,0,2,0,3,0,4,0,5,
								  0,5,0,4,0,3,0,2,0,
								  1,0,2,0,3,0,4,0,5,
								  0,5,0,4,0,3,0,2,0,
								  1,0,2,0,3,0,4,0,5,
								  0,5,0,4,0,3,0,2,0,
								  1,0,2,0,3,0,4,0,5};

class BlockBreakerGame
{
public:
	shader colorShader;
	texShader textureShader;
private:
	enum gameStates
	{
		breakingState,
		servingState,
		nextLevelState,
		gameOverState
	};

	enum tileColors
	{
		RED,
		GREEN,
		BLUE,
		YELLOW,
		PURPLE,
	};

	gameStates gameState;

	//shader colorShader;
	//texShader textureShader;
	GLint viewportWidth;
	GLint viewportHeight;

	backGround bkgrnd;
	GLuint backTexture;
	float bkgrndHalfX;
	float bkgrndHalfY;
	float bkgrndCenterX;
	float bkgrndCenterY;

	paddle myPaddle;
	vec4 paddleColor;
	float paddleHalfX;
	float paddleHalfY;
	float paddleCenterX;
	float paddleCenterY;

	ball myBall;
	vec4 ballColor;
	vec4 newBallPos;
	float ballHalfX;
	float ballHalfY;

	levels gameLevels;
	std::vector<tile> tiles;
	int widthInTiles;
	int heightInTiles;
	int counter;
	vec4 tileColor;
	float tileCenterX;
	float tileCenterY;
	float tileHalfX;
	float tileHalfY;

	int playerScore;

	char keys[256];

	float courtSize() 
	{ 
		return 1.0f; 
	}

	void drawWorld(shader &colShader) 
	{	
		myBall.draw(colShader);
		myPaddle.draw(colShader);

		for (unsigned int i=0; i<tiles.size(); i++)
		{
			tiles[i].draw(colShader);
		}
	}
	

	void serve() {
		// while serving, glue the ball to the server's bat
		vec4 offset = vec4(0,0.1f, 0, 0);
		myBall.set_pos(myPaddle.pos() + offset);
		if (keys[' ']) 
		{
			gameState = breakingState;
			myBall.setVelocity(vec4(0.02f,0.02f,0.0f,0.0f));
			//ball_velocity = vec4(server ? -ball_speed() : ball_speed(), -ball_speed(), 0, 0);
		}
	}

	void Score(int player) 
	{
		playerScore++;
		if (playerScore > 10) 
		{
			gameState = gameOverState;
		} 
	}

	void startBreaking() 
	{
		moveBall();

		// bounce
		keepInBounds();
		collideWithBlocks();
		if (myBall.getVelocity()[1]<0)
		{
			collideWithPaddle();
		}
	}

	void keepInBounds()
	{
		//at the top of the screen
		if (myBall.getVelocity()[1] > 0 && newBallPos[1]+ballHalfY > courtSize()) 
		{
			myBall.setVelocity(myBall.getVelocity()* vec4(1, -1, 1, 1));  
		}//bottom
		else if (myBall.getVelocity()[1] < 0 && newBallPos[1]+ballHalfY < -courtSize())
		{
			gameState = servingState;
		}

		// right and left
		if (myBall.getVelocity()[0] > 0 && newBallPos[0]+ballHalfX > courtSize() ||
			myBall.getVelocity()[0] < 0 && newBallPos[0]-ballHalfX < -courtSize())
		{	
			myBall.setVelocity(myBall.getVelocity()* vec4(-1, 1, 1, 1));
		}
	}

	void collideWithPaddle()
	{
		if (myBall.getVelocity()[0]>0)
		{
			if (myBall.intersects(myPaddle)&&myBall.pos()[0]<myPaddle.pos()[0]) 
			{
				//myBall.setVelocity(myBall.getVelocity()* vec4(-1, -1, 1, 1));
				if (fabsf(myPaddle.pos()[0]-myBall.pos()[0])>0.075f)
				{
					myBall.setVelocity(vec4(0.02f,-0.02f,0,0)* vec4(-1.5f, -0.5f, 1, 1));
				}
				else if (fabsf(myPaddle.pos()[0]-myBall.pos()[0])<=0.075f&&fabs(myBall.pos()[0]-myPaddle.pos()[0])>0.05f)
				{
					myBall.setVelocity(vec4(0.02f,-0.02f,0,0)* vec4(-1.2f, -0.80f, 1, 1));
				}
				else
				{
					myBall.setVelocity(vec4(0.02f,-0.02f,0,0)* vec4(-1.05f, -0.95f, 1, 1));
				}
			}
			else if (myBall.intersects(myPaddle)&&myBall.pos()[0]>=myPaddle.pos()[0])
			{
				//myBall.setVelocity(myBall.getVelocity()* vec4(1, -1, 1, 1));
				if (fabsf(myBall.pos()[0]-myPaddle.pos()[0])>0.075f)
				{
					myBall.setVelocity(vec4(0.02f,-0.02f,0,0)* vec4(1.5f, -0.5f, 1, 1));
				}
				else if (fabsf(myBall.pos()[0]-myPaddle.pos()[0])<=0.075f&&fabsf(myBall.pos()[0]-myPaddle.pos()[0])>0.05f)
				{
					myBall.setVelocity(vec4(0.02f,-0.02f,0,0)* vec4(1.20f, -0.8f, 1, 1));
				}
				else
				{
					myBall.setVelocity(vec4(0.02f,-0.02f,0,0)* vec4(1.05f, -0.95f, 1, 1));
				}
			}
		}
		else
		{
			if (myBall.intersects(myPaddle)&&myBall.pos()[0]<myPaddle.pos()[0]) 
			{
				//myBall.setVelocity(myBall.getVelocity()* vec4(1, -1, 1, 1));
				if (myPaddle.pos()[0]-myBall.pos()[0]>0.075f)
				{
					myBall.setVelocity(vec4(-0.02f,-0.02f,0,0)* vec4(1.5f, -0.5f, 1, 1));
				}
				else if (myPaddle.pos()[0]-myBall.pos()[0]<=0.075f&&myBall.pos()[0]-myPaddle.pos()[0]>0.05f)
				{
					myBall.setVelocity(vec4(-0.02f,-0.02f,0,0)* vec4(1.20f, -0.80f, 1, 1));
				}
				else
				{
					myBall.setVelocity(vec4(-0.02f,-0.02f,0,0)* vec4(1.05f, -0.95f, 1, 1));
				}
			}
			else if (myBall.intersects(myPaddle)&&myBall.pos()[0]>=myPaddle.pos()[0])
			{
				//myBall.setVelocity(myBall.getVelocity()* vec4(-1, -1, 1, 1));
				if (myBall.pos()[0]-myPaddle.pos()[0]>0.75f)
				{
					myBall.setVelocity(vec4(-0.02f,-0.02f,0,0)* vec4(-1.5f, -0.5f, 1, 1));
				}
				else if (myBall.pos()[0]-myPaddle.pos()[0]<=0.75f&&myBall.pos()[0]-myPaddle.pos()[0]>0.5f)
				{
					myBall.setVelocity(vec4(-0.02f,-0.02f,0,0)* vec4(-1.20f, -0.80f, 1, 1));
				}
				else
				{
					myBall.setVelocity(vec4(-0.02f,-0.02f,0,0)* vec4(-1.05f, -0.95f, 1, 1));
				}
			}
		}
	}

	void collideWithBlocks()
	{
		static int counter = 0;
		for (unsigned int i=0; i<tiles.size(); i++)
		{
			if (myBall.intersects(tiles[i]))
			{
				if (myBall.pos()[0]<tiles[i].pos()[0]-tileHalfX)
				{
					myBall.setVelocity(myBall.getVelocity()* vec4(-1, 1, 1, 1));
					tiles.erase(tiles.begin()+i);
				}
				else if (myBall.pos()[0]>tiles[i].pos()[0]+tileHalfX)
				{
					myBall.setVelocity(myBall.getVelocity()* vec4(-1, 1, 1, 1));
					tiles.erase(tiles.begin()+i);
				}
				else if (myBall.pos()[1]>tiles[i].pos()[1]-tileHalfX)
				{
					myBall.setVelocity(myBall.getVelocity()* vec4(1, -1, 1, 1));
					tiles.erase(tiles.begin()+i);
				}
				else if (myBall.pos()[1]<tiles[i].pos()[1]+tileHalfX)
				{
					myBall.setVelocity(myBall.getVelocity()* vec4(1, -1, 1, 1));
					tiles.erase(tiles.begin()+i);
				}
			}
		}
	}

	void movePaddle() 
	{
		// look at the keys and move the paddle
		vec4 paddleRight(0.03f, 0, 0, 0);
		
		if (keys['a']) 
		{
			if (myPaddle.pos()[0]-paddleHalfX<-1.0f)
			{
				myPaddle.pos()[0]=-1.0f+paddleHalfX;
			}
			else
			{
				myPaddle.move(-paddleRight);
			}
		}
		if (keys['s']) 
		{
			if (myPaddle.pos()[0]+paddleHalfX>1.0f)
			{
				myPaddle.pos()[0]=1.0f-paddleHalfX;
			}
			else
			{
				myPaddle.move(paddleRight);
			}
		}
	}

	void moveBall()
	{
		newBallPos = myBall.pos() + myBall.getVelocity();
		myBall.set_pos(newBallPos);
	}

	void simulate() 
	{
		movePaddle();

		if (gameState == servingState) 
		{
			serve();
		} 
		else if (gameState == breakingState) 
		{
			startBreaking();
		}
	}

	void render() {
		simulate();

		// clear the frame buffer and the depth
		glClearColor(0, 0, 0, 1);
		glViewport(0, 0, viewportWidth, viewportHeight);
		glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
		
		//bkgrnd.draw(textureShader,backTexture);
		drawWorld(colorShader);
		
		// swap buffers so that the image is displayed.
		// gets a new buffer to draw a new scene.
		glutSwapBuffers();
	}

	BlockBreakerGame()
	{
		memset(keys, 0, sizeof(keys));

		bkgrndHalfX = 1.0f;
		bkgrndHalfY = 1.0f;
		bkgrndCenterX = 0;
		bkgrndCenterY = 0;
		backTexture = texture_manager::new_texture("assets/space1.tga",0,0,800,800);
		bkgrnd = backGround(bkgrndCenterX,bkgrndCenterY,bkgrndHalfX,bkgrndHalfY,backTexture);

		playerScore = 0;

		paddleHalfX = 0.20f;
		paddleHalfY = 0.02f;
		paddleCenterX = 0;
		paddleCenterY = -1+paddleHalfY * 4;
		paddleColor = vec4(1.0f,1.0f,1.0f,1.0f);
		myPaddle = paddle(paddleCenterX,paddleCenterY,paddleHalfX,paddleHalfY,paddleColor);
		
		ballHalfX = 0.01f;
		ballHalfY = 0.01f;
		ballColor = vec4(0.8f,0.8f,0.8f,1.0f);
		myBall=ball(0, 0, ballHalfX, ballHalfY,ballColor);

		gameLevels = levels();
		widthInTiles = 9;
		heightInTiles = 9;
		tileHalfX = 0.1f;
		tileHalfY = 0.05f;
		counter = 0;
		for (int i=0; i<heightInTiles; i++)
		{
			for (int j=0; j<widthInTiles; j++)
			{
				tileCenterX = -0.8f+(j*2*tileHalfX);
				tileCenterY = 0.8f-(i*2*tileHalfY);
				if (gameLevels.firstLevel[counter]!=0)
				{
					switch (gameLevels.firstLevel[counter])
					{
						case RED:
							tileColor = vec4(1.0f,0,0,0);
							break;
						case GREEN:
							tileColor = vec4(0,1.0f,0,0);
							break;
						case BLUE:
							tileColor = vec4(0,0,1.0f,0);
							break;
						case YELLOW:
							tileColor = vec4(1.0f,1.0f,0,0);
							break;
						case PURPLE:
							tileColor = vec4(0.5f,0,1.0f,0);
							break;
					}
					tiles.push_back(tile(tileCenterX,tileCenterY,
						tileHalfX,tileHalfY,tileColor));
				}
				counter++;
			}
		}
		// set up a simple shader to render textures
		textureShader.init
			(
			// just copy the position attribute to gl_Position
			"varying vec2 uv_;"
			"attribute vec3 pos;"
			"attribute vec2 uv;"
			"void main() { gl_Position = vec4(pos * 0.05, 1); uv_ = uv; }",

			// just copy the color attribute to gl_FragColor
			"varying vec2 uv_;"
			"uniform sampler2D texture;"
			"void main() { gl_FragColor = texture2D(texture, uv_); }"
			//"void main() { gl_FragColor = vec4(1, 0, 0, 1); }"
			);

		// set up a simple shader to render the emissive color
		colorShader.init
			(
			// just copy the position attribute to gl_Position
			"attribute vec4 pos;"
			"void main() { gl_Position = pos; }",

			// just copy the color attribute to gl_FragColor
			"uniform vec4 emissive_color;"
			"void main() { gl_FragColor = emissive_color; }"
			);


		gameState = servingState;
	}

	// The viewport defines the drawing area in the window
	void setViewport(int w, int h) 
	{
		viewportWidth = w;
		viewportHeight = h;
	}

	void setKey(int key, int value) 
	{
		keys[key & 0xff] = value;
	}

	public:
		// a singleton: one instance of this class only!
		static BlockBreakerGame &get()
		{
			static BlockBreakerGame singleton;
			return singleton;
		}

		// interface from GLUT
		static void reshape(int w, int h) { get().setViewport(w, h); }
		static void display() { get().render(); }
		static void timer(int value) { glutTimerFunc(30, timer, 1); glutPostRedisplay(); }
		static void keyDown( unsigned char key, int x, int y) { get().setKey(key, 1); }
		static void keyUp( unsigned char key, int x, int y) { get().setKey(key, 0); }
};

// boilerplate to run the sample
int main(int argc, char **argv)
{
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA|GLUT_DEPTH|GLUT_DOUBLE);
  glutInitWindowSize(800, 800);
  glutCreateWindow("BlockBreaker");
  glewInit();
  if (!glewIsSupported("GL_VERSION_2_0") )
  {
    printf("OpenGL 2 is required!\n");
    return 1;
  }
  
  glutDisplayFunc(BlockBreakerGame::display);
  glutReshapeFunc(BlockBreakerGame::reshape);
  glutKeyboardFunc(BlockBreakerGame::keyDown);
  glutKeyboardUpFunc(BlockBreakerGame::keyUp);
  glutTimerFunc(30, BlockBreakerGame::timer, 1);
  glutMainLoop();
  return 0;
}

